<?php

// ------------------------------------------------------------------------
// |@Author       : Jarmin <edshop@qq.com>
// |@----------------------------------------------------------------------
// |@Date         : 2025-04-06 09:31:10
// |@----------------------------------------------------------------------
// |@LastEditTime : 2025-04-26 19:39:59
// |@----------------------------------------------------------------------
// |@LastEditors  : Jarmin <jarmin@ladmin.cn>
// |@----------------------------------------------------------------------
// |@Description  : 仓储基类，提供模型自动加载功能
// |@----------------------------------------------------------------------
// |@FilePath     : Repository.php
// |@----------------------------------------------------------------------
// |@Copyright (c) 2025 http://www.ladmin.cn   All rights reserved.
// ------------------------------------------------------------------------

namespace think\admin;

use think\App;
use think\helper\Str;
use think\facade\Cache;

/**
 * 仓储基类
 *
 * 提供基础仓储功能，包括：
 * - 模型类自动加载
 * - 统一异常处理
 * - 缓存数据处理
 *
 * @package think\admin
 */
abstract class Repository
{
    /**
     * 应用实例
     * @var App
     */
    protected App $app;

    /**
     * 默认缓存时间（秒）
     * @var int
     */
    protected int $cacheExpire = 3600;

    /**
     * 是否启用缓存
     * @var bool
     */
    protected bool $cacheEnabled = true;

    /**
     * 构造函数
     * @param App $app 应用实例
     */
    public function __construct(App $app)
    {
        $this->app = $app;
    }

    /**
     * 获取缓存键名
     *
     * @param string $key 缓存键
     * @return string 完整的缓存键名
     */
    protected function getCacheKey(string $key): string
    {
        // 使用类名作为缓存分组，避免不同仓储间的缓存冲突
        $className = class_basename($this);
        return "{$className}:{$key}";
    }

    /**
     * 从缓存获取数据
     *
     * @param string $key 缓存键
     * @param callable $callback 当缓存不存在时的回调函数
     * @param int|null $expire 缓存有效期（秒），为null则使用默认时间
     * @return mixed 缓存数据或回调结果
     */
    protected function remember(string $key, callable $callback, ?int $expire = null): mixed
    {
        if (!$this->cacheEnabled) {
            return $callback();
        }

        $cacheKey = $this->getCacheKey($key);
        $expire = $expire !== null ? (int)$expire : (int)$this->cacheExpire;

        return Cache::remember($cacheKey, $callback, $expire);
    }

    /**
     * 设置缓存
     *
     * @param string $key 缓存键
     * @param mixed $value 缓存值
     * @param int|null $expire 缓存有效期（秒），为null则使用默认时间
     * @return bool 是否设置成功
     */
    protected function setCache(string $key, mixed $value, ?int $expire = null): bool
    {
        if (!$this->cacheEnabled) {
            return false;
        }

        $cacheKey = $this->getCacheKey($key);
        $expire = $expire !== null ? (int)$expire : (int)$this->cacheExpire;

        return Cache::set($cacheKey, $value, $expire);
    }

    /**
     * 获取缓存
     *
     * @param string $key 缓存键
     * @param mixed $default 默认值
     * @return mixed 缓存值或默认值
     */
    protected function getCache(string $key, mixed $default = null): mixed
    {
        if (!$this->cacheEnabled) {
            return $default;
        }

        $cacheKey = $this->getCacheKey($key);
        return Cache::get($cacheKey, $default);
    }

    /**
     * 删除缓存
     *
     * @param string $key 缓存键
     * @return bool 是否删除成功
     */
    protected function deleteCache(string $key): bool
    {
        $cacheKey = $this->getCacheKey($key);
        return Cache::delete($cacheKey);
    }

    /**
     * 清除当前仓储的所有缓存
     *
     * @return bool 是否清除成功
     */
    protected function clearCache(): bool
    {
        $className = class_basename($this);
        $pattern = "{$className}:*";

        // 使用标签清除缓存，如果标签不可用，则返回false
        try {
            Cache::tag($className)->clear();
            return true;
        } catch (\Throwable $e) {
            // 标签清除失败，尝试使用Redis的pattern删除
            try {
                $redis = Cache::store('redis')->handler();
                if ($redis && method_exists($redis, 'keys') && method_exists($redis, 'del')) {
                    $keys = $redis->keys($pattern);
                    if (!empty($keys)) {
                        $redis->del($keys);
                        return true;
                    }
                }
            } catch (\Throwable $e) {
                // 忽略Redis异常
            }
            return false;
        }
    }

    /**
     * 魔术方法-模型定位
     *
     * 通过属性访问自动加载模型类
     * 例如：$this->modelUser 会自动加载 User 模型类
     *
     * @param string $name 属性名
     * @return mixed
     * @throws HttpResponseException 当模型类不存在时抛出异常
     */
    public function __get(string $name): mixed
    {
        if (preg_match('/^model(\w+)$/i', $name, $matches)) {
            $className = Str::studly($matches[1]);
            return $this->loadModel($className);
        }
    }

    /**
     * 加载模型类
     *
     * 按照以下顺序查找模型类：
     * 1. think\admin\model 命名空间下
     * 2. 应用自定义模型类
     *
     * @param string $className 模型类名
     * @return mixed
     * @throws HttpResponseException 当模型类不存在时抛出500错误响应
     */
    protected function loadModel(string $className): mixed
    {
        $models = [
            "\\think\\admin\\model\\{$className}",
            $this->app->parseClass('model', $className),
        ];

        foreach ($models as $class) {
            if (class_exists($class)) {
                return $this->app->make($class);
            }
        }

        throw_response(500, lang('system.error_model_not_found', ['model' => $className]));
    }
}
